package com.linkgie.galaxyframework.utils.io;

import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;
import java.nio.file.CopyOption;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.FileVisitOption;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.StandardCopyOption;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.Enumeration;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.BiPredicate;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

import org.springframework.core.io.ClassPathResource;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.ResourceUtils;

import com.linkgie.galaxyframework.utils.BinaryUtils;
import com.linkgie.galaxyframework.utils.CollectionUtils;
import com.linkgie.galaxyframework.utils.IOStatusException;
import com.linkgie.galaxyframework.utils.NotFoundException;
import com.linkgie.galaxyframework.utils.SortedProperties;
import com.linkgie.galaxyframework.utils.SystemIOException;
import com.linkgie.galaxyframework.utils.TextUtils;
import com.linkgie.galaxyframework.utils.lang.ClassPath;
import com.linkgie.galaxyframework.utils.lang.JavaClassUtils;

public class FileUtils {

	public static final String DEFAULT_CHARSET = "UTF-8";

	public static final String CLASSPATH_URL_PREFIX = ResourceUtils.CLASSPATH_URL_PREFIX;

	private static final CopyOption[] EMPTY_COPY_OPTIONS = {};

	public static enum FilterOption {
		INCLUDE,

		EXCLUDE
	}

	/**
	 * 检查指定的资源是否存在；
	 * <p>
	 * 
	 * @param resourceLocation 如果参数以 classpath: 开通，则作为类路径资源进行检查；否则，以文件路径进行检查是否为文件；
	 * @return
	 */
	public static boolean existResource(String resourceLocation) {
		return existResource(resourceLocation, JavaClassUtils.getDefaultClassLoader());
	}

	/**
	 * 检查指定的资源是否存在；
	 * <p>
	 * 
	 * @param resourceLocation 如果参数以 classpath: 开通，则作为类路径资源进行检查；否则，以文件路径进行检查是否为文件；
	 * @return
	 */
	public static boolean existResource(String resourceLocation, Class<?> clazz) {
		return existResource(resourceLocation, clazz.getClassLoader());
	}

	/**
	 * 检查指定的资源是否存在；
	 * <p>
	 * 
	 * @param resourceLocation 如果参数以 classpath: 开通，则作为类路径资源进行检查；否则，以文件路径进行检查是否为文件；
	 * @return
	 */
	public static boolean existResource(String resourceLocation, ClassLoader classloader) {
		if (resourceLocation.startsWith(CLASSPATH_URL_PREFIX)) {
			// 对于打包到 jar 包中的资源文件，此操作将引发异常，需要通过流的方式来加载；
			String path = resourceLocation.substring(CLASSPATH_URL_PREFIX.length());
			ClassPathResource resource = new ClassPathResource(path, classloader);
			return resource.exists();
		}
		return existFile(resourceLocation);
	}

	public static boolean existFile(String filePath) {
		File file = new File(filePath);
		return file.isFile();
	}

	public static boolean existDirectory(String dir) {
		File file = new File(dir);
		return file.isDirectory();
	}

	public static boolean makeDirectory(String dir) {
		File file = new File(dir);
		return file.mkdirs();
	}

	/**
	 * 返回完整的绝对路径；
	 * 
	 * @param path path
	 * @return String
	 */
	public static String getFullPath(String path) {
		try {
			File file = new File(path);
			return file.getCanonicalPath();
		} catch (IOException e) {
			throw new IllegalStateException(e.getMessage(), e);
		}
	}

	/**
	 * 返回完整的绝对路径；
	 * 
	 * @param path path
	 * @return String
	 */
	public static String getFullPath(File file) {
		try {
			return file.getCanonicalPath();
		} catch (IOException e) {
			throw new IllegalStateException(e.getMessage(), e);
		}
	}

	/**
	 * 返回父目录的路径；
	 * 
	 * @param path path
	 * @return String
	 */
	public static String getParent(String path) {
		File file = new File(path);
		return file.getParent();
	}

	/**
	 * 以默认字符集（UTF-8）读取指定文件的首行；
	 * 
	 * @param file file
	 * @return String
	 * @throws IOException exception
	 */
	public static String readFirstLine(File file) throws IOException {
		return readFirstLine(file, DEFAULT_CHARSET);
	}

	/**
	 * 读取指定文件的首行；
	 * 
	 * @param file    file
	 * @param charset 字符集；
	 * @return 返回首行非空行；返回结果不会自动截取两头的空字符串；
	 * @throws IOException exception
	 */
	public static String readFirstLine(File file, String charset) throws IOException {
		FileInputStream in = new FileInputStream(file);
		try {
			InputStreamReader reader = new InputStreamReader(in, charset);
			return getFirstLine(reader);
		} finally {
			in.close();
		}
	}

	public static String[] readLines(File file) {
		return readLines(file, DEFAULT_CHARSET);
	}

	/**
	 * 返回指定文件的所有行；
	 * 
	 * @param file    file
	 * @param charset 字符集；
	 * @return 返回首行非空行；返回结果不会自动截取两头的空字符串；
	 */
	public static String[] readLines(File file, String charset) {
		try (FileInputStream in = new FileInputStream(file);
				InputStreamReader reader = new InputStreamReader(in, charset);) {
			return getLines(reader);
		} catch (IOException e) {
			throw new IOStatusException(e.getMessage(), e);
		}
	}

	/**
	 * 返回指定文件的所有行；
	 * 
	 * @param file    file
	 * @param charset 字符集；
	 * @return 返回首行非空行；返回结果不会自动截取两头的空字符串；
	 */
	public static String[] readLines(InputStream in, String charset) {
		try {
			InputStreamReader reader = new InputStreamReader(in, charset);
			return getLines(reader);
		} catch (IOException e) {
			throw new IOStatusException(e.getMessage(), e);
		}
	}

	public static void writeLines(String[] lines, File file) {
		writeLines(lines, file, DEFAULT_CHARSET);
	}

	public static void writeLines(String[] lines, File file, String charset) {
		try (FileOutputStream out = new FileOutputStream(file, false);
				OutputStreamWriter writer = new OutputStreamWriter(out, charset);
				BufferedWriter bfw = new BufferedWriter(writer);) {
			for (String line : lines) {
				writer.write(line);
				writer.write("\r\n");
			}

			bfw.flush();
			writer.flush();
			out.flush();
		} catch (IOException e) {
			throw new IOStatusException(e.getMessage(), e);
		}
	}

	public static String getFirstLine(Reader reader) throws IOException {
		BufferedReader bfr = reader instanceof BufferedReader ? (BufferedReader) reader : new BufferedReader(reader);
		try {
			String line = null;
			while ((line = bfr.readLine()) != null) {
				return line;
			}
			return null;
		} finally {
			bfr.close();
		}
	}

	public static String[] getLines(Reader reader) throws IOException {
		BufferedReader bfr = reader instanceof BufferedReader ? (BufferedReader) reader : new BufferedReader(reader);
		try {
			ArrayList<String> lines = new ArrayList<String>();
			String line = null;
			while ((line = bfr.readLine()) != null) {
				lines.add(line);
			}
			return lines.toArray(new String[lines.size()]);
		} finally {
			bfr.close();
		}
	}

	/**
	 * 以默认字符集（UTF-8）将指定的文本保存到指定的文件中；
	 * 
	 * @param file 要保存的文件；
	 * @param text 文本内容；
	 */
	public static void writeText(String text, String file) {
		writeText(text, new File(file), DEFAULT_CHARSET);
	}

	/**
	 * 以默认字符集（UTF-8）将指定的文本保存到指定的文件中；
	 * 
	 * @param text 文本内容；
	 * @param file 要保存的文件；
	 */
	public static void writeText(String text, File file) {
		writeText(text, file, DEFAULT_CHARSET);
	}

	/**
	 * 将指定的文本保存到指定的文件中；
	 * 
	 * @param text    文本内容；
	 * @param file    要保存的文件；
	 * @param charset 字符集；
	 */
	public static void writeText(String text, File file, String charset) {
		try (FileOutputStream out = new FileOutputStream(file, false)) {
			writeText(text, out, charset);
			out.flush();
		} catch (IOException e) {
			throw new IOStatusException(e.getMessage(), e);
		}
	}

	/**
	 * 以覆盖模式将指定的内容写入到指定文件；
	 * 
	 * @param content
	 * @param file
	 */
	public static void writeBytes(byte[] content, String file) {
		try (FileOutputStream out = new FileOutputStream(file, false)) {
			out.write(content);
			out.flush();
		} catch (IOException e) {
			throw new IOStatusException(e.getMessage(), e);
		}
	}

	/**
	 * 以覆盖模式将指定的内容写入到指定文件；
	 * 
	 * @param content
	 * @param file
	 */
	public static void writeBytes(byte[] content, File file) {
		try (FileOutputStream out = new FileOutputStream(file, false)) {
			out.write(content);
			out.flush();
		} catch (IOException e) {
			throw new IOStatusException(e.getMessage(), e);
		}
	}

	/**
	 * @param contentInput
	 * @param file
	 * @param appending
	 */
	public static void write(InputStream contentInput, File file) {
		write(contentInput, file, false);
	}

	/**
	 * @param contentInput
	 * @param file
	 * @param appending
	 */
	public static void write(InputStream contentInput, File file, boolean appending) {
		try (FileOutputStream out = new FileOutputStream(file, appending)) {
			BinaryUtils.copy(contentInput, out);
			out.flush();
		} catch (IOException e) {
			throw new IOStatusException(e.getMessage(), e);
		}
	}

	public static void append_UTF8(String content, File file) {
		append(BinaryUtils.toBytes_UTF8(content), file);
	}

	public static void append(String content, File file, String charset) {
		append(BinaryUtils.toBytes(content, charset), file);
	}

	public static void append(byte[] content, File file) {
		try (FileOutputStream out = new FileOutputStream(file, true)) {
			out.write(content);
			out.flush();
		} catch (IOException e) {
			throw new IOStatusException(e.getMessage(), e);
		}
	}

	@Deprecated
	public static void appendBytes(byte[] content, File file) {
		appendBytes(content, file);
	}

	public static void writeText(String text, OutputStream out, String charset) {
		try {
			// 注：不能关闭 writer，否则引发内部同时关闭输出流，导致输出流在后续的操作中失效；
			OutputStreamWriter writer = new OutputStreamWriter(out, charset);
			writer.write(text);
			writer.flush();
			out.flush();
		} catch (IOException e) {
			throw new IOStatusException(e.getMessage(), e);
		}
	}

	public static void writeText(String text, Writer writer) {
		try {
			writer.write(text);
			writer.flush();
		} catch (IOException e) {
			throw new IOStatusException(e.getMessage(), e);
		}
	}

	public static void writeProperties(Properties props, File file) {
		writeProperties(props, null, file);
	}

	public static void writeProperties(Properties props, String comments, File file) {
		try (FileOutputStream out = new FileOutputStream(file, false)) {
			writeProperties(props, comments, out, DEFAULT_CHARSET);
			out.flush();
		} catch (IOException e) {
			throw new IOStatusException(e.getMessage(), e);
		}
	}

	public static void writeProperties(Properties props, String comments, File file, String charset) {
		try (FileOutputStream out = new FileOutputStream(file, false)) {
			writeProperties(props, comments, out, charset);
			out.flush();
		} catch (IOException e) {
			throw new IOStatusException(e.getMessage(), e);
		}
	}

	public static void writeProperties(Properties props, OutputStream out) {
		writeProperties(props, null, out, DEFAULT_CHARSET);
	}

	public static void writeProperties(Properties props, String comments, OutputStream out) {
		writeProperties(props, comments, out, DEFAULT_CHARSET);
	}

	public static void writeProperties(Properties props, OutputStream out, String charset) {
		writeProperties(props, null, out, charset);
	}

	public static void writeProperties(Properties props, String comments, OutputStream out, String charset) {
		try {
			// 注：不能关闭 writer，否则引发内部同时关闭输出流，导致输出流在后续的操作中失效；
			OutputStreamWriter writer = new OutputStreamWriter(out, charset);
			props.store(writer, comments);
			writer.flush();
		} catch (IOException e) {
			throw new IOStatusException(e.getMessage(), e);
		}
	}

	/**
	 * 以默认字符集（UTF-8）从文件读取文本；
	 * 
	 * @param resourceLocation 文件位置信息，可支持绝对路径、相对路径（相对dir）、classpath：前缀；
	 * @return String
	 */
	public static String readText(String resourceLocation) throws NotFoundException {
		return readText(resourceLocation, DEFAULT_CHARSET);
	}

	/**
	 * 从文件读取文本；
	 * 
	 * @param resourceLocation 文件位置信息，可支持绝对路径、相对路径（相对dir）、classpath：前缀；
	 * @param charset          charset
	 * @return String
	 */
	public static String readText(String resourceLocation, String charset) throws NotFoundException {
		try (InputStream in = read(resourceLocation)) {
			return readText(in, charset);
		} catch (IOException e) {
			throw new IOStatusException(e.getMessage(), e);
		}
	}
	// return readText(new File(resourceLocation), charset);

	/**
	 * 以默认字符集（UTF-8）从文件读取文本；
	 * 
	 * @param file file
	 * @return String
	 */
	public static String readText(File file) {
		return readText(file, DEFAULT_CHARSET);
	}

	/**
	 * 从文件读取文本；
	 * 
	 * @param file    file
	 * @param charset charset
	 * @return String
	 */
	public static String readText(File file, String charset) {
		try {
			FileInputStream in = new FileInputStream(file);
			try {
				return readText(in, charset);
			} finally {
				in.close();
			}
		} catch (IOException e) {
			throw new IOStatusException(e.getMessage(), e);
		}
	}

	public static String readText(InputStream in) throws IOException {
		return readText(in, DEFAULT_CHARSET);
	}

	/**
	 * 从流读取文本；
	 * 
	 * @param in      in
	 * @param charset charset
	 * @return String
	 * @throws IOException exception
	 */
	public static String readText(InputStream in, String charset) throws IOException {
		InputStreamReader reader = new InputStreamReader(in, charset);
		try {
			StringBuilder content = new StringBuilder();
			char[] buffer = new char[64];
			int len = 0;
			while ((len = reader.read(buffer)) > 0) {
				content.append(buffer, 0, len);
			}
			return content.toString();
		} finally {
			reader.close();
		}
	}

	/**
	 * 读取文件的字节内容；
	 * 
	 * @param resourceLocation 文件位置信息，可支持绝对路径、相对路径（相对dir）、classpath：前缀；
	 * @return
	 */
	public static byte[] readBytes(String resourceLocation) {
		try (InputStream in = read(resourceLocation)) {
			return BinaryUtils.readBytes(in);
		} catch (IOException e) {
			throw new IOStatusException(e.getMessage(), e);
		}
	}

	/**
	 * 读取文件的字节内容；
	 * 
	 * @param resourceLocation 文件位置信息，可支持绝对路径、相对路径（相对dir）、classpath：前缀；
	 * @return
	 */
	public static byte[] readBytes(String resourceLocation, ClassLoader classLoader) {
		try (InputStream in = read(resourceLocation, null, classLoader)) {
			return BinaryUtils.readBytes(in);
		} catch (IOException e) {
			throw new IOStatusException(e.getMessage(), e);
		}
	}

	/**
	 * 读取文件的字节内容；
	 * 
	 * @param resourceLocation 文件位置信息，可支持绝对路径、相对路径（相对dir）、classpath：前缀；
	 * @return
	 */
	public static byte[] readBytes(String resourceLocation, String parentDir) {
		try (InputStream in = read(resourceLocation, parentDir)) {
			return BinaryUtils.readBytes(in);
		} catch (IOException e) {
			throw new IOStatusException(e.getMessage(), e);
		}
	}

	/**
	 * 读取文件的字节内容；
	 * 
	 * @param resourceLocation 文件位置信息，可支持绝对路径、相对路径（相对dir）、classpath：前缀；
	 * @return
	 */
	public static byte[] readBytes(String resourceLocation, String parentDir, ClassLoader classLoader) {
		try (InputStream in = read(resourceLocation, parentDir, classLoader)) {
			return BinaryUtils.readBytes(in);
		} catch (IOException e) {
			throw new IOStatusException(e.getMessage(), e);
		}
	}

	public static byte[] readBytes(File file) {
		try {
			FileInputStream in = new FileInputStream(file);
			try {
				return BinaryUtils.readBytes(in);
			} finally {
				in.close();
			}
		} catch (IOException e) {
			throw new IOStatusException(e.getMessage(), e);
		}
	}

	public static Properties readProperties(String resourceLocation) {
		return readProperties(resourceLocation, DEFAULT_CHARSET);
	}

	public static SortedProperties readSortedProperties(String resourceLocation) {
		return readSortedProperties(resourceLocation, DEFAULT_CHARSET);
	}

	public static Properties readProperties(File file) {
		return readProperties(file, DEFAULT_CHARSET);
	}

	public static Properties readProperties(String resourceLocation, String charset) {
		try (InputStream in = read(resourceLocation)) {
			return readProperties(in, charset);
		} catch (IOException e) {
			throw new IOStatusException(e.getMessage(), e);
		}
	}

	public static SortedProperties readSortedProperties(String resourceLocation, String charset) {
		try (InputStream in = read(resourceLocation)) {
			return readSortedProperties(in, charset);
		} catch (IOException e) {
			throw new IOStatusException(e.getMessage(), e);
		}
	}

	public static Properties readProperties(File file, String charset) {
		try {
			FileInputStream in = new FileInputStream(file);
			try {
				return readProperties(in, charset);
			} finally {
				in.close();
			}
		} catch (IOException e) {
			throw new IOStatusException(e.getMessage(), e);
		}
	}

	/**
	 * 从输入流中读取属性；
	 * <p>
	 * 注：此方法完成后不会关闭输入流;
	 * 
	 * @param in
	 * @return
	 */
	public static Properties readProperties(InputStream in) {
		return readProperties(in, DEFAULT_CHARSET);
	}

	/**
	 * 从输入流中读取属性，返回有序的属性表；
	 * <p>
	 * 注：此方法完成后不会关闭输入流
	 * 
	 * @param in
	 * @return 返回有序的属性表；
	 */
	public static SortedProperties readSortedProperties(InputStream in) {
		return readSortedProperties(in, DEFAULT_CHARSET);
	}

	/**
	 * 从输入流中读取属性；
	 * <p>
	 * 注：此方法完成后不会关闭输入流
	 * 
	 * @param in
	 * @param charset
	 * @return
	 */
	public static Properties readProperties(InputStream in, String charset) {
		return readProperties(in, charset, new Properties());
	}

	/**
	 * 从输入流中读取属性，返回有序的属性表；
	 * <p>
	 * 注：此方法完成后不会关闭输入流
	 * 
	 * @param in
	 * @param charset
	 * @return 返回有序的属性表；
	 */
	public static SortedProperties readSortedProperties(InputStream in, String charset) {
		return readProperties(in, charset, new SortedProperties());
	}

	/**
	 * 从输入流中读取属性；
	 * <p>
	 * 注：此方法完成后不会关闭输入流
	 * 
	 * @param in
	 * @param charset
	 * @param props
	 * @return
	 */
	public static <T extends Properties> T readProperties(InputStream in, String charset, T props) {
		try {
			InputStreamReader reader = new InputStreamReader(in, charset);
			try {
				props.load(reader);
				return props;
			} finally {
				reader.close();
			}
		} catch (IOException e) {
			throw new IOStatusException(e.getMessage(), e);
		}
	}

	public static Properties readPropertiesAndClose(InputStream in) {
		return readPropertiesAndClose(in, DEFAULT_CHARSET);
	}

	public static Properties readSortedPropertiesAndClose(InputStream in) {
		return readSortedPropertiesAndClose(in, DEFAULT_CHARSET);
	}

	public static Properties readPropertiesAndClose(InputStream in, String charset) {
		return readPropertiesAndClose(in, charset, new Properties());
	}

	public static SortedProperties readSortedPropertiesAndClose(InputStream in, String charset) {
		return readPropertiesAndClose(in, charset, new SortedProperties());
	}

	/**
	 * 从输入流中读取属性，完成后关闭输入流；
	 * 
	 * @param in
	 * @param charset
	 * @param props
	 * @return
	 */
	public static <T extends Properties> T readPropertiesAndClose(InputStream in, String charset, T props) {
		try {
			InputStreamReader reader = new InputStreamReader(in, charset);
			try {
				props.load(reader);
				return props;
			} finally {
				reader.close();
			}
		} catch (IOException e) {
			throw new IOStatusException(e.getMessage(), e);
		} finally {
			try {
				in.close();
			} catch (IOException e) {
				throw new IOStatusException(e.getMessage(), e);
			}
		}
	}

	/**
	 * 根据byte数组，生成文件 filePath 文件路径 fileName 文件名称（需要带后缀，如*.jar）
	 */
	public static File getFile(byte[] bfile, String filePath, String fileName) {
		BufferedOutputStream bos = null;
		FileOutputStream fos = null;
		File file = null;
		try {
			File dir = new File(filePath);
			if (!dir.exists()) {// 判断文件目录是否存在
				dir.mkdirs();
			}
			file = new File(filePath + File.separator + fileName);
			fos = new FileOutputStream(file);
			bos = new BufferedOutputStream(fos);
			bos.write(bfile);
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if (bos != null) {
				try {
					bos.close();
				} catch (IOException e1) {
					e1.printStackTrace();
				}
			}
			if (fos != null) {
				try {
					fos.close();
				} catch (IOException e1) {
					e1.printStackTrace();
				}
			}
		}
		return file;
	}

	public static String getCurrentDir() {
		try {
			return new File("./").getCanonicalPath();
		} catch (IOException e) {
			throw new IllegalStateException(e.getMessage(), e);
		}
	}

	public static void deleteFile(String dir) {
		deleteFile(dir, false);
	}

	public static void deleteFile(File file) {
		deletePath(file, false);
	}

	public static void deleteFile(String dir, boolean silent) {
		File directory = new File(dir);
		deletePath(directory, silent);
	}

	/**
	 * 删除文件；<br>
	 * 
	 * @param path   如果指定的路径是单个文件，则删除该文件；如果指定路径是目录，则删除该目录及其下的全部文件；
	 * @param silent 是否静默删除；如果为 true ，则吞噬删除过程中的异常， 意味着方法即便正常返回时也有可能删除不完全；
	 * @return 如果删除成功，则返回 true； 否则返回 false，或者抛出 {@link IOStatusException};
	 */
	public static boolean deletePath(File path, boolean silent) {

		return deleteAll(path.toPath(), silent);

		// 去掉递归删除的实现方法，改为采用更高效的基于NIO接口遍历删除方法；
		// if (path.isFile()) {
		// try {
		// return path.delete();
		// } catch (Exception e) {
		// if (!silent) {
		// throw new IOStatusException(e.getMessage(), e);
		// }
		// }

		// return false;
		// }

		// if (!path.isDirectory()) {
		// // 既不是文件，也不是目录，直接返回 true ；
		// return true;
		// }

		// // delete dir;
		// File[] files = path.listFiles();
		// if (files == null) {
		// return false;
		// }

		// for (File f : files) {
		// deletePath(f, silent);
		// }
		// return path.delete();
	}

	public static boolean deleteAll(Path path, boolean silent) {
		try {
			if (!Files.isDirectory(path)) {
				Files.deleteIfExists(path);
				return true;
			}

			Files.walkFileTree(path, new SimpleFileVisitor<Path>() {
				@Override
				public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
					Files.delete(file);
					return FileVisitResult.CONTINUE;
				}

				@Override
				public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
					if (exc != null) {
						throw exc;
					}
					Files.delete(dir);
					return FileVisitResult.CONTINUE;
				}
			});

			return true;
		} catch (IOException e) {
			if (!silent) {
				throw new IOStatusException(e.getMessage(), e);
			}

			return false;
		}
	}

	/**
	 * 获取指定路径和位置的文件信息;
	 * <p>
	 * 
	 * 对于打包到 jar 包中的资源文件，此操作将引发 FileNotFoundException 异常，需要通过流的方式来加载；
	 *
	 * @param resourceLocation 文件位置信息，可支持绝对路径、相对路径（相对dir）、classpath：前缀；
	 *                         如果指定了绝对路径或者类路径，则忽略目录参数 dir;
	 * @param parentDir        父目录的路径，不要以"/"结尾; 可选参数；
	 * @return
	 *
	 * @throws FileNotFoundException
	 */
	public static File getFile(String resourceLocation, String parentDir) throws FileNotFoundException {
		if (resourceLocation.startsWith(ResourceUtils.CLASSPATH_URL_PREFIX)) {
			// 对于打包到 jar 包中的资源文件，此操作将引发异常，需要通过流的方式来加载；
			return ResourceUtils.getFile(resourceLocation);
		}
		if (resourceLocation.startsWith(PathUtils.PATH_SEPERATOR)) {
			return new File(resourceLocation);
		}
		String totalPath = PathUtils.concatPaths(parentDir, resourceLocation);
		return new File(totalPath);
	}

	/**
	 * 获取指定路径和位置的文件信息;
	 * <p>
	 * 
	 * 对于打包到 jar 包中的资源文件，此操作将引发 FileNotFoundException 异常，需要通过流的方式来加载；
	 *
	 * @param resourceLocation 文件位置信息，可支持绝对路径、相对路径（相对dir）、classpath：前缀；
	 * @return
	 *
	 * @throws FileNotFoundException
	 */
	public static InputStream read(String resourceLocation) throws NotFoundException {
		return read(resourceLocation, (String) null);
	}

	/**
	 * 读取资源文件内容;
	 * <p>
	 * 
	 * 对于打包到 jar 包中的资源文件，此操作将引发 FileNotFoundException 异常，需要通过流的方式来加载；
	 *
	 * @param resourceLocation 文件位置信息，可支持绝对路径、相对路径（相对dir）、classpath：前缀；
	 *                         如果指定了绝对路径或者类路径，则忽略目录参数 dir;
	 * @param parentDir        父目录的路径，不要以"/"结尾; 可选参数；
	 * @return
	 *
	 * @throws FileNotFoundException
	 */
	public static InputStream read(String resourceLocation, String parentDir) throws NotFoundException {
		ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
		if (classLoader == null) {
			classLoader = FileUtils.class.getClassLoader();
		}

		return read(resourceLocation, parentDir, classLoader);
	}

	/**
	 * 读取资源文件内容;
	 * <p>
	 * 
	 * 对于打包到 jar 包中的资源文件，此操作将引发 FileNotFoundException 异常，需要通过流的方式来加载；
	 *
	 * @param resourceLocation 文件位置信息，可支持绝对路径、相对路径（相对dir）、classpath：前缀；
	 *                         如果指定了绝对路径或者类路径，则忽略目录参数 dir;
	 * @param parentDir        父目录的路径，不要以"/"结尾; 可选参数；
	 * @return
	 *
	 * @throws FileNotFoundException
	 */
	public static InputStream read(String resourceLocation, ClassLoader classLoader) throws NotFoundException {
		return read(resourceLocation, null, classLoader);
	}

	/**
	 * 读取资源文件内容;
	 * <p>
	 * 
	 * 对于打包到 jar 包中的资源文件，此操作将引发 FileNotFoundException 异常，需要通过流的方式来加载；
	 *
	 * @param resourceLocation 文件位置信息，可支持绝对路径、相对路径（相对dir）、classpath：前缀；
	 *                         如果指定了绝对路径或者类路径，则忽略目录参数 dir;
	 * @param parentDir        父目录的路径，不要以"/"结尾; 可选参数；
	 * @return
	 *
	 * @throws FileNotFoundException
	 */
	public static InputStream read(String resourceLocation, String parentDir, ClassLoader classLoader)
			throws NotFoundException {
		try {
			if (resourceLocation.startsWith(ResourceUtils.CLASSPATH_URL_PREFIX)) {
				// 对于打包到 jar 包中的资源文件，此操作将引发异常，需要通过流的方式来加载；
				String path = resourceLocation.substring(ResourceUtils.CLASSPATH_URL_PREFIX.length());

				ClassPathResource resource = new ClassPathResource(path, classLoader);
				return resource.getInputStream();
			}
			if (resourceLocation.startsWith(PathUtils.PATH_SEPERATOR) || TextUtils.isBlank(parentDir)) {
				return new FileInputStream(new File(resourceLocation));
			}

			String totalPath = PathUtils.concatPaths(parentDir, resourceLocation);
			return new FileInputStream(new File(totalPath));
		} catch (FileNotFoundException e) {
			throw new NotFoundException(e.getMessage(), e);
		} catch (IOException e) {
			throw new IOStatusException(e.getMessage(), e);
		}
	}

	/**
	 * 获取指定路径和位置的文件信息;
	 * <p>
	 * 
	 * 对于打包到 jar 包中的资源文件，此操作将引发 FileNotFoundException 异常，需要通过流的方式来加载；
	 *
	 * @param resourceLocation 文件位置信息，可支持绝对路径、相对路径（相对dir）、classpath：前缀；
	 *                         如果指定了绝对路径或者类路径，则忽略目录参数 dir;
	 * @param parentDir        父目录的路径，不要以"/"结尾; 可选参数；
	 * @return
	 *
	 * @throws FileNotFoundException
	 */
	public static Properties readPropertiesResouce(String resourceLocation, String parentDir)
			throws FileNotFoundException {
		try {
			InputStream in = read(resourceLocation, parentDir);
			try {
				return readProperties(in, DEFAULT_CHARSET);
			} finally {
				in.close();
			}
		} catch (IOException e) {
			throw new IOStatusException(e.getMessage(), e);
		}
	}

	/**
	 * 将指定的名称转为符合命名规范的文件名；
	 * <p>
	 * 
	 * 方法将名称中不符合规范的特殊字符（\, /, ./）都替换为下划线；
	 * 
	 * @param name
	 * @return
	 */
	public static String encodeAsFileName(String name) {
		try {
			return new File(name).getCanonicalFile().getName();
		} catch (IOException e) {
			throw new IOStatusException(e.getMessage(), e);
		}
	}

	/**
	 * 扫描指定扩展名的文件；
	 * 
	 * <p>
	 * 方法不递归扫描子目录；
	 * <p>
	 * 返回结果只包含普通文件（即 {@link File#isFile()} 为 true ），不包含目录；
	 * 
	 * @param dir     目录；
	 * @param extName 扩展名；
	 * @return 返回符合条件的所有文件；
	 */
	public static File[] listFiles(String dir, String extName) {
		File fDir = new File(dir);
		return listFiles(fDir, extName);
	}

	/**
	 * 扫描指定扩展名的文件；
	 * <p>
	 * 方法不递归扫描子目录；
	 * <p>
	 * 返回结果只包含普通文件（即 {@link File#isFile()} 为 true ），不包含目录；
	 * 
	 * @param dir     目录；
	 * @param extName 扩展名；
	 * @return 返回符合条件的所有文件；
	 */
	public static File[] listFiles(File dir, String extName) {
		File[] matcheFiles = dir.listFiles(new FileFilter() {
			@Override
			public boolean accept(File pathname) {
				return pathname.isFile() && pathname.getName().endsWith(extName);
			}
		});

		return matcheFiles;
	}

	// /**
	// * 扫描指定扩展名的文件；
	// *
	// * 不递归扫描子目录，返回结果也不包含名称符合条件的目录；
	// *
	// * @param dir 目录；
	// * @param extName 扩展名；
	// * @return 返回符合条件的所有文件；
	// */
	// public static String[] listFilePaths(String dir, String extName) {
	// File[] matcheFiles = listFiles(dir, extName);
	// return getFileAbsolutePaths(matcheFiles);
	// }

	public static String[] getFileAbsolutePaths(File... files) {
		if (files == null) {
			return null;
		}
		String[] paths = new String[files.length];
		for (int i = 0; i < files.length; i++) {
			paths[i] = files[i].getAbsolutePath();
		}
		return paths;
	}

	public static File[] getFiles(String parentDir, String... filePaths) {
		if (filePaths == null) {
			return null;
		}
		File[] files = new File[filePaths.length];
		for (int i = 0; i < filePaths.length; i++) {
			files[i] = new File(parentDir, filePaths[i]);
		}

		return files;
	}

	/**
	 * 是否为绝对路径；
	 * 
	 * @param path
	 * @return
	 */
	public static boolean isAbsolute(String path) {
		return new File(path).isAbsolute();
	}

	/**
	 * 是否为相对路径；
	 * 
	 * @param path
	 * @return
	 */
	public static boolean isRelative(String path) {
		return !new File(path).isAbsolute();
	}

	public static String getPath(String parent, String child) {
		return new File(parent, child).getPath();
	}

	/**
	 * 复制文件；
	 * <p>
	 * 
	 * 此方法基于 NIO 执行操作；
	 * 
	 * @param from
	 * @param to
	 */
	public static long copy(File from, File to) {
		try (InputStream in = Files.newInputStream(from.toPath());
				OutputStream out = Files.newOutputStream(to.toPath())) {
			long totalBytes = BinaryUtils.copy(in, out);
			return totalBytes;
		} catch (IOException e) {
			throw new IOStatusException(e.getMessage(), e);
		}
	}

	/**
	 * 复制目录；
	 * <p>
	 * 
	 * 此方法基于 NIO 执行操作；
	 * 
	 * @param from
	 * @param to
	 */
	public static void copyDirectory(File from, File to) {
		copyDirectory(from, to, false);
	}

	/**
	 * 复制目录；
	 * <p>
	 * 
	 * 此方法基于 NIO 执行操作；
	 * 
	 * @param from
	 * @param to
	 * @param override
	 */
	public static void copyDirectory(File from, File to, boolean override) {
		copyDirectory(from, to, override, false);
	}

	/**
	 * 复制目录；
	 * <p>
	 * 
	 * 此方法基于 NIO 执行操作；
	 * 
	 * @param from          来源目录；
	 * @param to            目标目录；
	 * @param override      是否覆盖已经存在的目标；
	 * @param abortIfExists 当目标已经存在时是否中止操作； 当参数为 true ，中止复制，以抛出异常的方式； 当参数为 false
	 *                      ，继续复制；
	 */
	public static void copyDirectory(File from, File to, boolean override, boolean abortIfExists) {
		final Path source = from.toPath();
		final Path target = to.toPath();

		try {
			Files.walkFileTree(source, EnumSet.of(FileVisitOption.FOLLOW_LINKS), Integer.MAX_VALUE,
					new SimpleFileVisitor<Path>() {
						@Override
						public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs)
								throws IOException {
							Path targetdir = target.resolve(source.relativize(dir));
							try {
								Files.copy(dir, targetdir);
							} catch (FileAlreadyExistsException e) {
								if (!Files.isDirectory(targetdir)) {
									throw e;
								}
							}
							return FileVisitResult.CONTINUE;
						}

						@Override
						public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
							CopyOption[] options;
							if (override) {
								options = new CopyOption[] { StandardCopyOption.REPLACE_EXISTING };
							} else {
								options = EMPTY_COPY_OPTIONS;
							}
							try {
								Path targetfile = target.resolve(source.relativize(file));
								Files.copy(file, targetfile, options);
							} catch (FileAlreadyExistsException e) {
								if (abortIfExists) {
									throw e;
								}
							}
							return FileVisitResult.CONTINUE;
						}
					});
		} catch (IOException e) {
			throw new FileExistsException(e);
		}
	}

	/**
	 * 扫描指定目录，列出其中的所有文件或子目录；
	 * <p>
	 * 返回结果不包含扫描的目录本身；
	 * <p>
	 * 如果指定的参数不是目录，则返回空列表；
	 * 
	 * @param rootDirectory
	 * @return
	 */
	public static List<File> listFiles(File rootDirectory) {
		return listFiles(rootDirectory, (f) -> f);
	}

	/**
	 * 扫描指定目录，列出其中的所有文件或子目录；
	 * <p>
	 * 返回结果不包含扫描的目录本身；
	 * <p>
	 * 如果指定的参数不是目录，则返回空列表；
	 * 
	 * @param rootDirectory
	 * @return
	 */
	public static List<File> listFiles(File rootDirectory, boolean recursively, boolean includeSubDirs) {
		return listFiles(rootDirectory, recursively, includeSubDirs, (f) -> f);
	}

	/**
	 * 扫描指定目录，列出其中的所有文件或子目录；
	 * <p>
	 * 返回结果不包含扫描的目录本身；
	 * <p>
	 * 如果指定的参数不是目录，则返回空列表；
	 * 
	 * @param <R>
	 * @param rootDirectory
	 * @param mapper
	 * @return
	 */
	public static <R> List<R> listFiles(File rootDirectory, Function<File, R> mapper) {
		if (!rootDirectory.isDirectory()) {
			return Collections.emptyList();
		}
		List<R> allFiles = listFiles(rootDirectory.toPath(), (path) -> mapper.apply(path.toFile()));
		return allFiles;
	}

	/**
	 * 扫描指定目录，列出其中的所有文件或子目录；
	 * <p>
	 * 返回结果不包含扫描的目录本身；
	 * <p>
	 * 如果指定的参数不是目录，则返回空列表；
	 * 
	 * @param <R>
	 * @param rootDirectory
	 * @param recursively    是否递归地进入子目录扫描；
	 * @param includeSubDirs 返回结果是否包含子目录；
	 * @param mapper         映射接口；
	 * @return
	 */
	public static <R> List<R> listFiles(File rootDirectory, boolean recursively, boolean includeSubDirs,
			Function<File, R> mapper) {
		if (!rootDirectory.isDirectory()) {
			return Collections.emptyList();
		}
		List<R> allFiles = listFiles(rootDirectory.toPath(), recursively, includeSubDirs,
				(path) -> mapper.apply(path.toFile()));
		return allFiles;
	}

	/**
	 * 扫描指定目录，列出其中的所有文件或子目录；
	 * <p>
	 * 返回结果不包含扫描的目录本身；
	 * <p>
	 * 如果指定的参数不是目录，则返回空列表；
	 * 
	 * @param <R>
	 * @param rootDirectory
	 * @param mapper        将文件路径转换为返回对象的映射器；
	 * @return
	 */
	public static <R> List<R> listFiles(Path rootPath, Function<Path, R> mapper) {
		return listFiles(rootPath, true, true, mapper);
	}

	/**
	 * 扫描指定目录，列出其中的所有文件或子目录；
	 * <p>
	 * 返回结果不包含扫描的目录本身；
	 * <p>
	 * 如果指定的参数不是目录，则返回空列表；
	 * 
	 * @param <R>            返回的类型；
	 * @param rootPath       要扫描的目录；
	 * @param recursively    是否递归地进入子目录扫描；
	 * @param includeSubDirs 返回结果是否包含子目录；
	 * @param mapper         将文件路径转换为返回对象的映射器；
	 * @return
	 */
	public static <R> List<R> listFiles(Path rootPath, boolean recursively, boolean includeSubDirs,
			Function<Path, R> mapper) {
		return listFiles(rootPath, recursively, includeSubDirs, false, mapper);
	}

	/**
	 * 扫描指定目录，列出其中的所有文件或子目录；
	 * <p>
	 * 返回结果不包含扫描的目录本身；
	 * <p>
	 * 如果指定的参数不是目录，则返回空列表；
	 * 
	 * @param <R>            返回的类型；
	 * @param rootPath       要扫描的目录；
	 * @param recursively    是否递归地进入子目录扫描；
	 * @param includeSubDirs 返回结果是否包含子目录；
	 * @param includeRoot    是否包含根路径；
	 * @param mapper         将文件路径转换为返回对象的映射器；
	 * @return
	 */
	public static <R> List<R> listFiles(Path rootPath, boolean recursively, boolean includeSubDirs, boolean includeRoot,
			Function<Path, R> mapper) {
		try {
			List<R> allFiles = new LinkedList<>();

			Files.walkFileTree(rootPath, EnumSet.of(FileVisitOption.FOLLOW_LINKS), Integer.MAX_VALUE,
					new SimpleFileVisitor<Path>() {
						@Override
						public FileVisitResult preVisitDirectory(Path dirPath, BasicFileAttributes attrs)
								throws IOException {
							// 根目录是必须要扫描的，不实施递归策略，
							if (dirPath.equals(rootPath)) {
								// 当前是根目录；
								// 由于指定了不包含根目录，忽略根目录；
								if (includeRoot) {
									R retn = mapper.apply(dirPath);
									allFiles.add(retn);
								}

								return FileVisitResult.CONTINUE;
							}
							if (includeSubDirs) {
								R retn = mapper.apply(dirPath);
								allFiles.add(retn);
							}
							return recursively ? FileVisitResult.CONTINUE : FileVisitResult.SKIP_SUBTREE;
						}

						@Override
						public FileVisitResult visitFile(Path filePath, BasicFileAttributes attrs) throws IOException {
							R retn = mapper.apply(filePath);
							allFiles.add(retn);
							return FileVisitResult.CONTINUE;
						}
					});
			return allFiles;
		} catch (IOException e) {
			throw new IOStatusException(e.getMessage(), e);
		}
	}

	public static void listFiles(Path rootPath, BiConsumer<Path, Boolean> consumer) {
		try {
			Files.walkFileTree(rootPath, EnumSet.of(FileVisitOption.FOLLOW_LINKS), Integer.MAX_VALUE,
					new SimpleFileVisitor<Path>() {
						@Override
						public FileVisitResult preVisitDirectory(Path dirPath, BasicFileAttributes attrs)
								throws IOException {
							consumer.accept(dirPath, Boolean.TRUE);
							return FileVisitResult.CONTINUE;
						}

						@Override
						public FileVisitResult visitFile(Path filePath, BasicFileAttributes attrs) throws IOException {
							consumer.accept(filePath, Boolean.FALSE);
							return FileVisitResult.CONTINUE;
						}
					});
		} catch (IOException e) {
			throw new IllegalStateException(e.getMessage(), e);
		}
	}

	/**
	 * 遍历目录；
	 * 
	 * @param directory
	 * @param predicate 诊断函数；参数返回 true 时，方法继续遍历目录树；参数返回 false 时，中止遍历目录树；
	 *                  <p>
	 *                  函数接受两个参数：
	 *                  <p>
	 *                  参数1：类型{@link File}，目录或文件；
	 *                  <p>
	 *                  参数2：类型 {@link Boolean}，表示参数1是否为目录；<br>
	 *                  如果参数1表示目录，则为 {@link Boolean#TRUE}； <br>
	 *                  如果参数1表示文件，则为 {@link Boolean#FALSE};
	 */
	public static void walkTree(File directory, BiPredicate<File, Boolean> predicate) {
		try {
			Files.walkFileTree(directory.toPath(), EnumSet.of(FileVisitOption.FOLLOW_LINKS), Integer.MAX_VALUE,
					new SimpleFileVisitor<Path>() {
						@Override
						public FileVisitResult preVisitDirectory(Path dirPath, BasicFileAttributes attrs)
								throws IOException {
							boolean notStop = predicate.test(dirPath.toFile(), Boolean.TRUE);
							return notStop ? FileVisitResult.CONTINUE : FileVisitResult.TERMINATE;
						}

						@Override
						public FileVisitResult visitFile(Path filePath, BasicFileAttributes attrs) throws IOException {
							boolean notStop = predicate.test(filePath.toFile(), Boolean.FALSE);
							return notStop ? FileVisitResult.CONTINUE : FileVisitResult.TERMINATE;
						}
					});
		} catch (IOException e) {
			throw new IOStatusException(e.getMessage(), e);
		}
	}

	/**
	 * 扫描指定目录中的文件；
	 * 
	 * @param directory
	 * @param ignoredFileNamePatterns 忽略文件的文件名匹配模式；注意：要匹配的输入参数是文件名，不包含文件的路径；
	 * @return 返回目录中的文件的相对路径清单；
	 */
	public static List<String> scanFilesAndExclude(File directory, String... ignoredFileNamePatterns) {
		return scanFiles(directory, null, FilterOption.EXCLUDE, ignoredFileNamePatterns);
	}

	/**
	 * 扫描指定目录中的文件；
	 * 
	 * @param directory
	 * @param ignoredFileNamePatterns 忽略文件的文件名匹配模式；注意：要匹配的输入参数是文件名，不包含文件的路径；
	 * @return 返回目录中的文件的相对路径清单；
	 */
	public static List<String> scanFilesAndExclude(File directory, String[] includedDirs,
			String... ignoredFileNamePatterns) {
		return scanFiles(directory, includedDirs, FilterOption.EXCLUDE, ignoredFileNamePatterns);
	}

	/**
	 * 扫描指定的目录；
	 * 
	 * @param directory        要扫描的目录；
	 * @param fileNamePatterns 文件名的匹配模式；注意：要匹配的输入参数是文件名，不包含文件的路径；
	 * @return 返回扫描到的匹配文件的相对路径清单；
	 */
	public static List<String> scanFiles(File directory, String... fileNamePatterns) {
		return scanFiles(directory, null, FilterOption.INCLUDE, fileNamePatterns, (path, subpath) -> subpath);
	}

	/**
	 * 扫描指定的目录；
	 * 
	 * @param directory        要扫描的目录；
	 * @param fileNamePatterns 文件名的匹配模式；注意：要匹配的输入参数是文件名，不包含文件的路径；
	 * @return 返回扫描到的匹配文件的相对路径清单；
	 */
	public static List<File> scanFileList(File directory, String... fileNamePatterns) {
		return scanFiles(directory, null, FilterOption.INCLUDE, fileNamePatterns, (path, subpath) -> path.toFile());
	}

	/**
	 * 扫描指定的目录；
	 * 
	 * @param directory        要扫描的目录；
	 * @param filterOptions    过滤模式选项；
	 * @param fileNamePatterns 文件名的匹配模式；注意：要匹配的输入参数是文件名，不包含文件的路径；
	 * @return 返回扫描到的匹配文件的相对路径清单；
	 */
	public static List<String> scanFiles(File directory, FilterOption filterOptions,
			String... fileNamePatterns) {
		return scanFiles(directory, null, filterOptions, fileNamePatterns, (path, subpath) -> subpath);
	}

	/**
	 * 扫描指定的目录；
	 * 
	 * @param directory        要扫描的目录；
	 * @param includedDirs     要扫描的子目录清单；参数项可以是通配符的模式字符串；只有匹配的目录才会被扫描，未匹配的目录以及子目录都将被忽略；
	 *                         当为空时，扫描全部；
	 * @param filterOptions    过滤模式选项；
	 * @param fileNamePatterns 文件名的匹配模式；注意：要匹配的输入参数是文件名，不包含文件的路径；
	 * @return 返回扫描到的匹配文件的相对路径清单；
	 */
	public static List<String> scanFiles(File directory, String[] includedDirs, FilterOption filterOptions,
			String... fileNamePatterns) {
		return scanFiles(directory, includedDirs, filterOptions, fileNamePatterns, (path, subpath) -> subpath);
	}

	/**
	 * 扫描指定的目录；
	 * 
	 * @param <R>
	 * @param directory           要扫描的目录；
	 * @param includedDirPatterns 要扫描的子目录清单；参数项可以是通配符的模式字符串；只有匹配的目录才会被扫描，未匹配的目录以及子目录都将被忽略；
	 *                            当为空时，扫描全部；
	 * @param filterOptions       过滤模式选项；
	 * @param fileNamePatterns    文件名的匹配模式；注意：要匹配的输入参数是文件名，不包含文件的路径；
	 * @param mapper              映射接口；将扫描到的路径转换为返回值的元素类型；
	 * @return 返回由映射接口转换后的结果列表；
	 */
	public static <R> List<R> scanFiles(File directory, String[] includedDirPatterns, FilterOption filterOptions,
			String[] fileNamePatterns, BiFunction<Path, String, R> mapper) {

		AntPathMatcher matcher = pathMatcher();

		// 采取排除策略；
		if (filterOptions == FilterOption.EXCLUDE) {
			return scanFiles(directory, includedDirPatterns, (filePath) -> {
				// 未指定忽略清单时，允许所有文件；
				if (fileNamePatterns == null || fileNamePatterns.length == 0) {
					return false;
				}
				String name = getName(filePath);
				return matchAny(name, matcher, fileNamePatterns);
			}, mapper);
		}

		// 采取包含策略；
		return scanFiles(directory, includedDirPatterns, (filePath) -> {
			// 未指定包含清单时，包含所有文件；
			if (fileNamePatterns == null || fileNamePatterns.length == 0) {
				return false;
			}
			String name = getName(filePath);
			return !matchAny(name, matcher, fileNamePatterns);
		}, mapper);
	}

	/**
	 * 扫描指定的目录；
	 * 
	 * <p>
	 * 此方法会递归遍历所有子目录；
	 * 
	 * @param directory
	 * @param ignorePredicate 忽略诊断函数；函数参数接收文件在扫描目录下的相对路径，如果函数返回 true
	 *                        ，则忽略文件，该文件不包含在返回结果；
	 * @return 返回路径和文件；
	 */
	public static List<String> scanFiles(File directory, Predicate<String> ignorePredicate) {
		return scanFiles(directory, null, ignorePredicate);
	}

	/**
	 * 扫描指定的目录；
	 * 
	 * @param rootDirectory        要扫描的目录；
	 * @param includedDirPatterns  要扫描的子目录清单；参数项可以是通配符的模式字符串；只有匹配的目录才会被扫描，未匹配的目录以及子目录都将被忽略；
	 *                             当为空时，扫描全部；
	 * @param ignoreFilesPredicate 忽略文件诊断函数；仅针对文件进行过滤，不过滤目录；函数的参数值是文件在扫描目录下的相对路径，如果函数返回
	 *                             true
	 *                             ，则忽略文件，该文件不包含在返回结果；
	 * @return 文件路径清单；文件路径是相对路径，相对于 rootDirectory 参数指定的根目录；
	 */
	public static List<String> scanFiles(File rootDirectory, String[] includedDirPatterns,
			Predicate<String> ignoreFilesPredicate) {
		return scanFiles(rootDirectory, includedDirPatterns, ignoreFilesPredicate, (path, subpath) -> {
			return subpath;
		});
	}

	/**
	 * 扫描指定的目录；
	 * 
	 * @param rootDirectory       要扫描的目录；
	 * @param includedDirPatterns 要扫描的子目录清单；参数项可以是通配符的模式字符串；只有匹配的目录才会被扫描，未匹配的目录以及子目录都将被忽略；
	 *                            当为空时，扫描全部；
	 * @param excludedPredicate   忽略文件诊断函数；仅针对文件进行过滤，不过滤目录；函数的参数值是文件在扫描目录下的相对路径，如果函数返回
	 *                            true
	 *                            ，则忽略文件，该文件不包含在返回结果；
	 * @return 文件路径清单；文件路径是相对路径，相对于 rootDirectory 参数指定的根目录；
	 */
	public static <R> List<R> scanFiles(File rootDirectory, String[] includedDirPatterns,
			Predicate<String> excludedPredicate, BiFunction<Path, String, R> mapper) {
		if (rootDirectory.isFile()) {
			throw new IllegalArgumentException("The specified root directory is actually a file!");
		}
		if (!rootDirectory.exists()) {
			return Collections.emptyList();
		}

		// 扫描所有文件，并计算哈希，之后对结果按照文件的相对路径排序；
		// 注：按照文件路径的字面文字排序，不依赖于文件系统遍历的顺序；
		List<R> filePaths = new LinkedList<>();
		int rootPrefixLength = rootDirectory.getAbsolutePath().length();

		String[] normalizedIncludedDirPatterns = normalizeToDirPatterns(includedDirPatterns);

		AntPathMatcher matcher = pathMatcher();
		try {
			Files.walkFileTree(rootDirectory.toPath(), EnumSet.of(FileVisitOption.FOLLOW_LINKS), Integer.MAX_VALUE,
					new SimpleFileVisitor<Path>() {
						@Override
						public FileVisitResult preVisitDirectory(Path dirPath, BasicFileAttributes attrs)
								throws IOException {
							// 遍历会从根目录开始，如果是根目录，则继续遍历；
							// String absolutePath = dirPath.toFile().getAbsolutePath();
							// if (absolutePath.length() == rootPrefixLength) {
							// // 这是根目录，根目录替代为；
							// return FileVisitResult.CONTINUE;
							// }

							// // 处理子目录；
							// boolean included;
							// if (normalizedIncludedDirPatterns == null ||
							// normalizedIncludedDirPatterns.length == 0) {
							// // 未指定目录匹配模式，则扫描所有；
							// included = true;
							// } else {
							// String absolutePath = dirPath.toFile().getAbsolutePath();
							// String relativePath = absolutePath.length() == rootPrefixLength ? ""
							// : absolutePath.substring(rootPrefixLength + 1);
							// String normalizedPath = normalizeToAbsuluteAntPathPatterns(relativePath);
							// included = matchAny(normalizedPath, matcher, normalizedIncludedDirPatterns);
							// }
							// // 忽略指定子目录之外的路径；
							// return included ? FileVisitResult.CONTINUE : FileVisitResult.SKIP_SUBTREE;

							return FileVisitResult.CONTINUE;
						}

						@Override
						public FileVisitResult visitFile(Path filePath, BasicFileAttributes attrs) throws IOException {
							String absolutePath = filePath.toFile().getAbsolutePath();
							String relativePath = absolutePath.substring(rootPrefixLength + 1);

							// 先匹配目录；
							boolean includedDir;
							if (normalizedIncludedDirPatterns == null || normalizedIncludedDirPatterns.length == 0) {
								// 未指定目录匹配模式，则扫描所有；
								includedDir = true;
							} else {
								String dir = getDir(relativePath);
								String normalizedDir = normalizeToDirPattern(dir);
								includedDir = matchAny(normalizedDir, matcher, normalizedIncludedDirPatterns);
							}
							// 如果目录不匹配，则忽略剩余所有的文件；
							if (!includedDir) {
								return FileVisitResult.CONTINUE;
							}

							// 检查文件；
							if (!excludedPredicate.test(relativePath)) {
								R item = mapper.apply(filePath, relativePath);
								filePaths.add(item);
							}
							return FileVisitResult.CONTINUE;
						}
					});
		} catch (IOException e) {
			throw new IOStatusException(e.getMessage(), e);
		}

		return filePaths;
	}

	/**
	 * 规范化指定的路径通配符为绝对路径的通配符；
	 * <p>
	 * <p>
	 * 如果不是以路径分隔符开头，则在开头加上路径分隔符；
	 * 
	 * @param patterns
	 * @return
	 */
	private static String[] normalizeToDirPatterns(String... patterns) {
		return CollectionUtils.cast(patterns, String.class, p -> normalizeToDirPattern(p));
	}

	/**
	 * 规范化指定的路径通配符为目录路径的通配符；
	 * <p>
	 * <p>
	 * 如果不是以路径分隔符结尾，则在最后加上路径分隔符；
	 * 
	 * @param pattern
	 * @return
	 */
	private static String normalizeToDirPattern(String pattern) {
		if (pattern.length() == 0) {
			return File.separator;
		}
		if (pattern.charAt(pattern.length() - 1) != File.separatorChar) {
			return pattern + File.separator;
		}
		return pattern;
	}

	/**
	 * Ant 路径通配符；
	 * 
	 * <p>
	 * 注：默认对大小写敏感；
	 * 
	 * @return
	 */
	private static AntPathMatcher pathMatcher() {
		return pathMatcher(true);
	}

	/**
	 * 创建路径匹配器；
	 * 
	 * @param caseSensitive 是否大小写敏感；
	 * @return
	 */
	private static AntPathMatcher pathMatcher(boolean caseSensitive) {
		AntPathMatcher matcher = new AntPathMatcher(File.separator);
		matcher.setCaseSensitive(caseSensitive);
		return matcher;
	}

	/**
	 * 检查指定路径是否匹配指定的 Ant 通配符清单的其中任意一个；
	 * <p>
	 * 如果 Ant 通配符清单为空，则返回 false ；
	 * 
	 * <p>
	 * 注：检查过程对大小写敏感；
	 * 
	 * @param path        要检查的路径；
	 * @param antPatterns Ant通配符清单；
	 * @return
	 */
	public static boolean matchAny(String path, String... antPatterns) {
		return matchAny(path, pathMatcher(), antPatterns);
	}

	/**
	 * 检查指定路径是否匹配指定的 Ant 通配符清单的其中任意一个；
	 * <p>
	 * 如果 Ant 通配符清单为空，则返回 false ；
	 * 
	 * @param path          要检查的路径；
	 * @param caseSensitive 是否大小写敏感；
	 * @param antPatterns   Ant通配符清单；
	 * @return
	 */
	public static boolean matchAny(String path, boolean caseSensitive, String... antPatterns) {
		return matchAny(path, pathMatcher(caseSensitive), antPatterns);
	}

	/**
	 * 检查指定路径是否匹配指定的 Ant 通配符清单的其中任意一个；
	 * <p>
	 * 如果 Ant 通配符清单为空，则返回 false ；
	 * 
	 * <p>
	 * 注：检查过程对大小写敏感；
	 * 
	 * @param path        要检查的路径；
	 * @param antPatterns Ant通配符清单；
	 * @return
	 */
	public static boolean matchAny(String path, Collection<String> antPatterns) {
		return matchAny(path, pathMatcher(), antPatterns);
	}

	/**
	 * 检查指定路径是否匹配指定的 Ant 通配符清单的其中任意一个；
	 * <p>
	 * 如果 Ant 通配符清单为空，则返回 false ；
	 * 
	 * @param path          要检查的路径；
	 * @param caseSensitive 是否大小写敏感；
	 * @param antPatterns   Ant通配符清单；
	 * @return
	 */
	public static boolean matchAny(String path, boolean caseSensitive, Collection<String> antPatterns) {
		return matchAny(path, pathMatcher(caseSensitive), antPatterns);
	}

	/**
	 * 检查指定的路径是否匹配指定模式中的任意一个；
	 * 
	 * @param path
	 * @param matcher
	 * @param patterns
	 * @return
	 */
	private static boolean matchAny(String path, AntPathMatcher matcher, String... patterns) {
		for (String pattern : patterns) {
			if (matcher.match(pattern, path)) {
				return true;
			}
		}
		return false;
	}

	/**
	 * 检查指定的路径是否匹配指定模式中的任意一个；
	 * 
	 * @param path
	 * @param matcher
	 * @param patterns
	 * @return
	 */
	private static boolean matchAny(String path, AntPathMatcher matcher, Collection<String> patterns) {
		for (String pattern : patterns) {
			if (matcher.match(pattern, path)) {
				return true;
			}
		}
		return false;
	}

	/**
	 * 返回扩展名；
	 * <p>
	 * 扩展名是路径名称的最后一个字符 “.” 至结尾的部分，字符 “.” 之后至少要有 1 个字符；
	 * 
	 * @param templateFile
	 * @return 返回扩展名(含字符 “.”)；如果没有，则返回 null；
	 */
	public static String getExtName(File templateFile) {
		return getExtName(templateFile.getName());
	}

	/**
	 * 返回扩展名；
	 * <p>
	 * 扩展名是路径名称的最后一个字符 “.” 至结尾的部分，字符 “.” 之后至少要有 1 个字符；
	 * 
	 * @param templateFile
	 * @return 返回扩展名(含字符 “.”)；如果没有，则返回 null；
	 */
	public static String getExtName(String fileName) {
		if (fileName == null || fileName.length() == 0) {
			return null;
		}
		int idx = fileName.lastIndexOf('.');

		return (idx < 0 || idx == fileName.length() - 1) ? null : fileName.substring(idx);
	}

	/**
	 * 返回路径的名称，即最后一个路径分隔符之后部分；
	 * 
	 * <p>
	 * 如果没有路径分隔符，则返回指定的路径本身；
	 * 
	 * @param path
	 * @return
	 */
	public static String getName(String path) {
		int index = path.lastIndexOf(File.separatorChar);
		if (index < 0) {
			// 当未找到分隔符时，换一种分隔再尝试；
			char anotherSeperatorChar = File.separatorChar == '/' ? '\\' : '/';
			index = path.lastIndexOf(anotherSeperatorChar);
		}
		if (index < 0) {
			return path;
		}
		return path.substring(index + 1);
	}

	/**
	 * 返回路径的目录，即最后一个路径分隔符之前的部分，包含最后的路径分隔符；
	 * <p>
	 * 如果没有路径分隔符，则返回长度为 0 的空字符串；
	 * 
	 * @param path
	 * @return
	 */
	public static String getDir(String path) {
		int index = path.lastIndexOf(File.separatorChar);
		if (index < 0) {
			return "";
		}
		return path.substring(0, index + 1);
	}

	/**
	 * 导出指定的资源；
	 * 
	 * @param zip             资源所在的 zip 包；
	 * @param path            要导出的资源路径；
	 *                        如果指向一个目录，则目录下的所有文件，包括子级目录的文件都将被导出；
	 *                        如果直线给一个文件，则只有该文件会被导出；
	 * @param outputDirectory 要导出的目录；如果该目录不存在，则会尝试创建目录；
	 * @param trimPath        是否要修剪路径导出路径；设置 true 时，并且当指定的资源路径是一个目录时，则导出的文件将不会包含
	 *                        path 参数的路径，而是以余下的相对路径作为文件在输出目录 outputDirectory 中的子路径；
	 */
	public static void extractZip(ZipFile zip, String path, File outputDirectory, boolean trimPath) {
		if (outputDirectory.isFile()) {
			throw new IllegalArgumentException("The specified output directory is actually a file!");
		}
		if (!outputDirectory.isDirectory()) {
			outputDirectory.mkdirs();
		}

		String sourcePath = JavaClassUtils.getCanonicalClasspath(path);
		Enumeration<? extends ZipEntry> entries = zip.entries();
		// 遍历所有的文件，找到以指定路径开头的文件；
		while (entries.hasMoreElements()) {
			ZipEntry zipEntry = entries.nextElement();
			if (zipEntry.isDirectory()) {
				// 略过目录，只导出文件，如果目录下没有文件，则不会导出；
				continue;
			}
			String targetName = zipEntry.getName();
			if (!targetName.startsWith(sourcePath)) {
				continue;
			}
			if (trimPath) {
				if (targetName.length() > sourcePath.length()) {
					// 当指定的路径过长时，可能会导致截取失败；
					targetName = targetName.substring(sourcePath.length() + 1);
				} else {
					targetName = FileUtils.getName(targetName);
				}
			}
			if (targetName.length() == 0) {
				continue;
			}

			try {
				InputStream in = zip.getInputStream(zipEntry);
				File targetFile = new File(outputDirectory, targetName);

				File targetDir = targetFile.getParentFile();
				if (!targetDir.exists()) {
					targetDir.mkdirs();
				}

				write(in, targetFile);
			} catch (IOException e) {
				throw new SystemIOException(e.getMessage(), e);
			}

		}
	}

	/**
	 * 将指定类路径下的资源导出到目录；
	 * 
	 * @param classpath
	 * @param outputDirectory
	 */
	public static void extractResources(String resourcePath, File outputDirectory) {
		extractResources(resourcePath, JavaClassUtils.getDefaultClassLoader(), outputDirectory);
	}

	/**
	 * 将指定类路径下的资源导出到目录；
	 * 
	 * <p>
	 * 方法将覆盖目录已经存在的同名文件；
	 * 
	 * @param classpath
	 * @param outputDirectory
	 */
	public static void extractResources(String resourcePath, ClassLoader classloader, File outputDirectory) {
		String resourceLocation = getCanonicalPath(resourcePath);
		if (resourceLocation.length() == 0) {
			throw new IllegalArgumentException("Resource path is empty!");
		}

		// 对于打包到 jar 包中的资源文件，此操作将引发异常，需要通过流的方式来加载；
		try {
			ClassPath classpath = JavaClassUtils.getClassPath(resourceLocation, classloader);
			if (classpath == null) {
				throw new IllegalArgumentException(
						"The resource path[" + resourceLocation + "] is not exist!  -- orig path: " + resourcePath);
			}
			if (classpath.isInJar()) {
				// 类路径指向的内容处于一个 jar 文件中；
				// 输出 jar 包中类路径范围内的相关资源；
				try (JarFile jarFile = new JarFile(classpath.getFile());) {
					extractZip(jarFile, resourceLocation, outputDirectory, true);
				}
			} else {
				if (classpath.getFile() != null) {
					// 类路径指向的是一个具体的文件；覆盖同名文件；
					File sourceFile = new File(classpath.getFile());
					if (!outputDirectory.exists()) {
						outputDirectory.mkdirs();
					}
					String fileName = sourceFile.getName();
					File targetFile = new File(outputDirectory, fileName);
					copyDirectory(sourceFile, targetFile, true);
				} else {
					// 类路径指向的是一个目录；覆盖同名文件；
					File dir = new File(classpath.getDir());
					copyDirectory(dir, outputDirectory, true);
				}
			}
		} catch (IOException e) {
			throw new SystemIOException(e.getMessage(), e);
		}
	}

	/**
	 * 列出指定类路径下的资源；
	 * 
	 * <p>
	 * 如果指定的类路径位于 jar 包中，则尝试解析在 jar
	 * 包中类路径对应的项({@link JarEntry})的类型：如果是文件，则直接返回文件对应的类路径；如果是目录，则返回目录下的子目录和文件的类路径；
	 * <p>
	 * 如果指定的类路径位于文件系统的目录中，且类路径表示一个具体目录，则解析返回目录下的子目录和文件；
	 * <p>
	 * 如果指定的类路径位于文件系统的目录中，且类路径表示一个具体的文件，则返回文件本身的路径；
	 * <p>
	 * 如果指定的路径不存在，则返回空数组；
	 * <p>
	 * 注：此方法不会对目录下的子目录递归地进行深度遍历；
	 * 
	 * @param resourcePath 资源路径；
	 * @param classloader  加载器；
	 * @return 如果存在子项，则返回直接子项的资源路径数组；<br>
	 *         如果不存在，则返回空数组 {@link TextUtils#EMPTY_ARRAY} ；
	 *         <p>
	 *         可以保证的是，返回的每一项资源路径，都以指定的 resourcePath 参数开头，无论 resourcePath
	 *         参数是绝对路径还是相对路径；<br>
	 *         如果 resourcePath 参数含有不规范字符（如多个路径分隔等），则会进行规范化转换后作为返回路径的前缀；相当于
	 *         {@value FileUtils#CLASSPATH_URL_PREFIX} 前缀 +
	 *         {@link #getCanonicalPath(resourcePath)}；
	 */
	public static String[] listResources(String resourcePath, ClassLoader classloader) {
		String resourceLocation = getCanonicalPath(resourcePath);
		if (resourceLocation.length() == 0) {
			return TextUtils.EMPTY_ARRAY;
		}
		// 对于打包到 jar 包中的资源文件，此操作将引发异常，需要通过流的方式来加载；

		ClassPath[] classpaths = JavaClassUtils.getClassPaths(resourceLocation, classloader);

		Set<String> resources = new LinkedHashSet<>();
		for (ClassPath classPath : classpaths) {
			String[] resList = listResources(resourceLocation, classPath);
			CollectionUtils.addAll(resources, resList);
		}
		return CollectionUtils.toArray(resources, String.class);
	}

	/**
	 * 列出指定类路径下的资源；
	 *
	 * @param resourceLocation
	 * @param classpath
	 * @return 如果存在子项，则返回直接子项的资源路径数组；<br>返回的每项资源路径，都以指定的 resourcePath
	 */
	private static String[] listResources(String resourceLocation, ClassPath classpath) {
		if (classpath.isInJar()) {
			JarFileSystem jarFile = openJar(classpath.getFile());
			return jarFile.list(classpath.getPath());
		} else if (classpath.getFile() != null) {
			// 类路径指向的是一个具体的文件，直接返回自身；
			String FILE_CLASSPATH = CLASSPATH_URL_PREFIX + resourceLocation;
			return new String[] { FILE_CLASSPATH };
		} else if (classpath.getDir() != null) {
			// 类路径指向的是一个目录；
			File dir = new File(classpath.getDir());
			if (dir.isDirectory()) {
				// 目录下的子项名称；
				String[] items = dir.list();
				// 加上根路径；
				String prefix = CLASSPATH_URL_PREFIX + resourceLocation;
				String seperator = prefix.endsWith(JavaClassUtils.CLASSPATH_SEPERATOR) ? ""
						: JavaClassUtils.CLASSPATH_SEPERATOR;
				for (int i = 0; i < items.length; i++) {
					String name = items[i];
					items[i] = prefix + seperator + name;
				}
				return items;
			}
		}
		// 目录不存在；
		return TextUtils.EMPTY_ARRAY;
	}

	/**
	 * 检查指定的路径是否是类型路径；
	 * 
	 * 类路径是以 {@link #CLASSPATH_URL_PREFIX} 前缀开头的路径；
	 * 
	 * @param path
	 * @return
	 */
	public static boolean isClassPath(String path) {
		return TextUtils.startsWithIgnoreCase(path, CLASSPATH_URL_PREFIX);
	}

	

	/**
	 * 返回规范化的路径；
	 * 
	 * @param resourcePath
	 * @return
	 */
	public static String getCanonicalPath(String resourcePath) {
		String resourceLocation = resourcePath;
		if (isClassPath(resourcePath)) {
			resourceLocation = resourcePath.substring(CLASSPATH_URL_PREFIX.length());
		}
		resourceLocation = JavaClassUtils.getCanonicalClasspath(resourceLocation);
		return resourceLocation;
	}

	public static ZipFileSystem createZip(File file) {
		ZipFileSystemImpl zipFs = new ZipFileSystemImpl(file);
		zipFs.createFileSystem();

		return zipFs;
	}

	public static ZipFileSystem createZip(String file) {
		ZipFileSystemImpl zipFs = new ZipFileSystemImpl(file);
		zipFs.createFileSystem();

		return zipFs;
	}

	public static ZipFileSystem openZip(File file) {
		ZipFileSystemImpl zipFs = new ZipFileSystemImpl(file);
		zipFs.openFileSystem();

		return zipFs;
	}

	public static ZipFileSystem openZip(String file) {
		ZipFileSystemImpl zipFs = new ZipFileSystemImpl(file);
		zipFs.openFileSystem();

		return zipFs;
	}

	public static JarFileSystem createJar(File file) {
		JarFileSystemImpl zipFs = new JarFileSystemImpl(file);
		zipFs.createFileSystem();

		return zipFs;
	}

	public static JarFileSystem createJar(String file) {
		JarFileSystemImpl zipFs = new JarFileSystemImpl(file);
		zipFs.createFileSystem();

		return zipFs;
	}

	public static JarFileSystem openJar(String file) {
		JarFileSystemImpl zipFs = new JarFileSystemImpl(file);
		zipFs.openFileSystem();

		return zipFs;
	}

	public static JarFileSystem openJar(File file) {
		JarFileSystemImpl zipFs = new JarFileSystemImpl(file);
		zipFs.openFileSystem();

		return zipFs;
	}

	// -----------------------

}
